home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 7
/
Apprentice-Release7.iso
/
Source Code
/
Pascal
/
Code Resources
/
Eclectic CDEFs
/
Gauss CDEF Folder
/
NeoTextBox.p
< prev
Wrap
Text File
|
1997-02-25
|
19KB
|
416 lines
{ NeoTextBox: Faster, script-aware alternative to TETextBox }
{}
{ Originally written in C by Bryan K. Ressler, from Develop issue 9 }
{ Pascal conversion by Peter N. Lewis }
{ Modifications by Sebastiano Pilla }
{}
{ 1) Added optional saving/restoring of the clip region. This is controlled by the inSwapClipping parameter: if set to }
{ TRUE, then NeoTextBox saves the current clip region, clips its drawing to inWrapBox, then restores the previously }
{ saved clip region at the end of the drawing; if set to FALSE, then NeoTextBox uses whatever clip region is current }
{}
{ 2) Added a check to avoid drawing text outside of inWrapBox, because it will be clipped anyway. However, the entire text }
{ must still be traversed to determine the correct line breaks }
{}
{ 3) Added a special one-line case, to avoid the wrapping loop where it is known that the text fits all into the given box }
unit NeoTextBox;
interface
uses
Types;
const
ntbJustFull = 128;
{ NeoTextBox }
{}
{ Word-wraps text inside a given box }
{}
{ Entry: inTextPtr = pointer to the text we need to wrap }
{ inTextLen = length (in bytes) of the text }
{ inWrapBox = the box within which we're wrapping }
{ inAlign = the text alignment }
{ teForceLeft, teFlushLeft -> left justified }
{ teJustCenter, teCenter -> center justified }
{ teJustRight, teFlushRight -> right justified }
{ ntbJustFull -> full justified }
{ teJustLeft, teFlushDefault -> system justified }
{ inLhCode = line height code }
{ < 0 -> variable - based on tallest character }
{ 0 -> default - based on GetFontInfo }
{ > 0 -> fixed - use inLhCode as the line height }
{ inSwapClipping = TRUE if NeoTextBox should save and restore the clipping, FALSE if this is unnecessary }
{ Exit: outEndY = vertical coordinate of the last line, if non-zero }
{ outLhUsed = line height used to draw the text, if non-zero }
{ function result = total number of lines drawn (even outside of the given box) }
function NeoTextBox (inTextPtr: Ptr;
inTextLen: UInt32;
inWrapBox: Rect;
inAlign: SInt16;
inLhCode: SInt16;
var outEndY: SInt16;
var outLhUsed: SInt16;
inSwapClipping: Boolean): SInt16;
implementation
uses
Fonts, FixMath, QuickDrawText, Script, TextUtils;
const
kReturnChar = $D;
{ Max }
{}
{ Returns the maximum between the two given numbers }
{}
{ Entry: inNum1 = first number }
{ inNum2 = second number }
{ Exit: function result = maximum between inNum1 and inNum2 }
function Max (inNum1, inNum2: SInt32): SInt32;
inline
$201F, $2E9F, $B097, $6F02, $2E80;
{ AddPtrLong }
{}
{ Adds an offset to the given pointer }
{}
{ Entry: inPtr = pointer (first operand) }
{ inOffset = offset (second operand) to be added to inPtr }
{ Exit: function result = pointer to Ord4(inPtr) + inOffset }
function AddPtrLong (inPtr: univ Ptr;
inOffset: SInt32): Ptr;
inline
$201F, { move.l (sp)+,d0 ; pop inOffset }
$D09F, { add.l (sp)+,d0 ; add inPtr to inOffset (and pop inPtr) }
$2E80; { move.l d0,(sp) ; place in result }
{ NTBLineHeight }
{}
{ Figures line height }
{}
{ Entry: inTextPtr = entire text given to NeoTextBox }
{ inTextLen = length (in bytes) of the text }
{ inWrapBox = box within we're wrapping }
{ inLhCode = line height code given to NeoTextBox }
{ Exit: outStartY = starting vertical pen location }
{ function result = line height to use }
function NTBLineHeight (inTextPtr: Ptr;
inTextLen: UInt32;
inWrapBox: Rect;
inLhCode: SInt16;
var outStartY: SInt16): UInt16;
var
fInfo: FontInfo; { old-style font information record }
frac: Point; { fraction for the TrueType calls }
asc, desc: SInt16; { used in the OutlineMetrics calls }
lineHeight: UInt16; { return value }
err: OSErr;
begin
GetFontInfo(fInfo);
if inLhCode < 0 then
begin
{ -------------------------------------------------------------------------------------------------- }
{ If the user has specified variable-height lines, we need to try to determine the tallest ascent in the given text. }
{ We can only really do this if the font is a TrueType font. Otherwise, we punt and use }
{ old - fashioned GetFontInfo numbers . }
{ -------------------------------------------------------------------------------------------------- }
frac.h := 1;
frac.v := 1;
if IsOutline(frac, frac) then
begin
{ ---------------------------------------------------------------------------------------------- }
{ At this Point we know the current font is a TrueType font, so we do an OutlineMetrics call with our full text. }
{ It will put the tallest character ascent into asc, and the deepest descent into desc. Then we choose between }
{ whichever's most between the old-style ascent/descent and the numbers we get from the OutlineMetrics call. }
{ ---------------------------------------------------------------------------------------------- }
err := OutlineMetrics(UInt16(inTextLen), inTextPtr, frac, frac, asc, desc, nil, nil, nil);
lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
end
else
begin
{ ------------------------------------------------------------------------------------------------- }
{ At this Point we know the current font isn't TrueType, so we just use the old way of calculating line height. }
{ ------------------------------------------------------------------------------------------------- }
lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
end;
end
else if inLhCode = 0 then
begin
{ ---------------------------------------------------------------------------------------------------- }
{ If the user has specified "default" line height, he just wants us to get the line height from the FontInfo record. }
{ ---------------------------------------------------------------------------------------------------- }
lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
outStartY := inWrapBox.top + fInfo.ascent + fInfo.leading;
end
else
begin
{ ----------------------------------------------------------------------------------------------------- }
{ If the user has provided a specific line height, we just trust them. We can't really generate too good of a }
{ starting vertical coordinate, but we munge one together anyway. }
{ ----------------------------------------------------------------------------------------------------- }
lineHeight := inLhCode;
outStartY := inWrapBox.top + inLhCode + fInfo.leading;
end;
NTBLineHeight := lineHeight;
end;
{ NTBDraw }
{}
{ Draws a line with appropriate justification }
{}
{ Entry: inLineStartPtr = pointer to the beginning of the text for the current line }
{ inLineBytes = length (in bytes) of the text for this line }
{ inWrapBox = box within which we're wrapping }
{ inAlign = text alignment as specified by the user }
{ inCurY = our current vertical pen coordinate }
{ inBoxWidth = width of inWrapBox (since NeoTextBox already calculated it) }
{ inBreakCode = break code returned from StyledLineBreak }
procedure NTBDraw (inLineStartPtr: Ptr;
inLineBytes: SInt32;
inWrapBox: Rect;
inAlign, inCurY, inBoxWidth: SInt16;
inBreakCode: StyledLineBreakCode);
var
blackLen: UInt32; { length of non-white characters }
slop: SInt16; { number of pixels of slop for full just }
begin
{ ------------------------------------------------------------------------------------------------------------ }
{ The first thing we do here is determine the length of the "black" part of the current line. This excludes spaces, carriage }
{ returns, and other white stuff depending on the language. How do we know what to eliminate? We DON'T! So we ask our }
{ friend the Script Manager to do it for us. VisibleLength returns the number of bytes that we should use for pixel width }
{ calculations. }
{ ------------------------------------------------------------------------------------------------------------ }
blackLen := VisibleLength(inLineStartPtr, inLineBytes);
if inAlign = ntbJustFull then
begin
{ --------------------------------------------------------------------------------------------------------- }
{ For full justification, we need to calculate the "slop" space on the line that's not filled up by the text. Then we }
{ move to the margin and get ready to draw BUT WAIT! If this is the last line of a paragraph, we need to draw it with }
{ whatever the system justification is. So if the break code indicates we're at the end of the text, or we can find a }
{ carriage return there ourselves, we just change the text alignment to the current default system text alignment and }
{ fall through without drawing. If it's just another line within a paragraph, we use the handy Script Manager routine }
{ DrawJust to draw it justified. In languages like Arabic, full justification is performed differently than just spacing }
{ out the words. DrawJust handles cases like these correctly. }
{ Note that when we go looking for the carriage return at the end of the line, it's okay to simply index to the last Byte }
{ of the string. This is because the carriage return character, 0x0d, is in the control-code range, which is defined to }
{ never be the low Byte of a two Byte character. }
{ --------------------------------------------------------------------------------------------------------- }
slop := inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen);
MoveTo(inWrapBox.left, inCurY);
if (inBreakCode = smBreakOverflow) | (AddPtrLong(inLineStartPtr, inLineBytes - 1)^ = kReturnChar) then
inAlign := GetSysDirection
else
DrawJust(inLineStartPtr, blackLen, slop);
end;
{ ------------------------------------------------------------------------------------------------------------ }
{ For the rest of the text alignments (left, center, and right), we just move the pen to the right place and draw the text }
{ with DrawText. Note that the alignments that could have come into the NeoTextBox call have been resolved down to one }
{ of the four constants teForceLeft, teJustRight, teJustCenter, or ntbJustFull. }
{ ------------------------------------------------------------------------------------------------------------ }
case inAlign of
teForceLeft, teJustLeft:
MoveTo(inWrapBox.left, inCurY);
teJustRight:
MoveTo(inWrapBox.right - TextWidth(inLineStartPtr, 0, blackLen), inCurY);
teJustCenter:
MoveTo(inWrapBox.left + (inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen)) div 2, inCurY);
otherwise
;
end;
if inAlign <> ntbJustFull then
DrawText(inLineStartPtr, 0, inLineBytes);
end;
{ NeoTextBox }
{}
{ Word-wraps text inside a given box }
{}
{ Entry: inTextPtr = pointer to the text we need to wrap }
{ inTextLen = length (in bytes) of the text }
{ inWrapBox = the box within which we're wrapping }
{ inAlign = the text alignment }
{ inLhCode = line height code }
{ inSwapClipping = TRUE if NeoTextBox should save and restore the clipping }
{ Exit: outEndY = vertical coordinate of the last line, if non-zero }
{ outLhUsed = line height used to draw the text, if non-zero }
{ function result = total number of lines drawn (even outside of the given box) }
function NeoTextBox (inTextPtr: Ptr;
inTextLen: UInt32;
inWrapBox: Rect;
inAlign: SInt16;
inLhCode: SInt16;
var outEndY: SInt16;
var outLhUsed: SInt16;
inSwapClipping: Boolean): SInt16;
var
oldClip: RgnHandle; { saved clipping region }
lineStartPtr: Ptr; { pointer to beginning of a line }
textEndPtr: Ptr; { pointer to the end of input text }
fixedMax: Fixed; { boxWidth converted to fixed point }
wrapWid: Fixed; { width to wrap to }
lineBytes: SInt32; { number of bytes in one line }
textLeft: UInt32; { pointer to remaining bytes of text }
boxWidth: SInt16; { width of the wrapBox }
lineHeight: UInt16; { calculated line height }
curY: SInt16; { current vertical pen location }
lineCount: UInt16; { number of lines we've drawn }
breakCode: StyledLineBreakCode; { returned code from StyledLineBreak }
begin
{ ---------------------------------------------------------------------------------------------------------- }
{ Check the parameters we're given, and exit immediately if they're wrong. }
{ ---------------------------------------------------------------------------------------------------------- }
if (inTextPtr = nil) or (inTextLen = 0) then
Exit(NeoTextBox);
{ ---------------------------------------------------------------------------------------------------------- }
{ First, we save the old clipping region and clip to wrapBox, if requested. Then, figure the width of wrapBox, and make }
{ a fixed point version of it. Also, resolve the text alignment teFlushDefault to be the default system text alignment. }
{ ---------------------------------------------------------------------------------------------------------- }
if inSwapClipping then
begin
oldClip := NewRgn;
GetClip(oldClip);
ClipRect(inWrapBox);
end;
boxWidth := inWrapBox.right - inWrapBox.left;
if inAlign = teFlushDefault then
inAlign := GetSysDirection;
{ ---------------------------------------------------------------------------------------------------------- }
{ Now we call NTBLineHeight to calculate the appropriate line height. It also figures our starting vertical pen location in }
{ curY based on the line height and other metric parameters. Clear lineCount, set lineStartPtr to point to the beginning of }
{ the text, calculate textEnd for comparison to know when we're done, and preset textLeft to be all the text. }
{ ---------------------------------------------------------------------------------------------------------- }
lineHeight := NTBLineHeight(inTextPtr, inTextLen, inWrapBox, inLhCode, curY);
lineCount := 0;
lineStartPtr := inTextPtr;
textEndPtr := AddPtrLong(inTextPtr, inTextLen);
textLeft := inTextLen;
{ ------------------------------------------------------------------------------------------------------------ }
{ Special one-line case: if the width of the input text is less than boxWidth, we avoid the wrapping loop and call NTBDraw }
{ directly to draw the single line with the appropriate justification. }
{ Note that the TextWidth call uses a 16-bit value to specify the number of bytes, where the inTextLen parameter given }
{ to NeoTextBox is a 32-bit value. So, we hope that the width of inWrapBox is small enough to let us correctly handle this }
{ special case. }
{ ------------------------------------------------------------------------------------------------------------ }
if boxWidth > TextWidth(inTextPtr, 0, LoWrd(inTextLen)) then
begin
NTBDraw(inTextPtr, inTextLen, inWrapBox, inAlign, curY, boxWidth, smBreakOverflow);
textLeft := 0; { avoids entering the wrapping loop }
end;
{ ------------------------------------------------------------------------------------------------------------ }
{ This is the main wrap-and-draw loop. I bet you never thought wrapping text could be so easy... }
{ ------------------------------------------------------------------------------------------------------------ }
fixedMax := Long2Fix(boxWidth);
while textLeft > 0 do
begin
{ ------------------------------------------------------------------------------------------------------- }
{ Every line, we have to preset lineBytes to something non-zero. This tells StyledLineBreak that we're drawing the }
{ first format run on the line (of course, for us, there's only ONE format run total). Also preset wrapWid. }
{ StyledLineBreak will always modify lineBytes (to tell you how many bytes are on this line), and will modify }
{ wrapWid, so we have to reset them each line. }
{ ------------------------------------------------------------------------------------------------------- }
lineBytes := 1;
wrapWid := fixedMax;
breakCode := StyledLineBreak(lineStartPtr, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
{ ------------------------------------------------------------------------------------------------------ }
{ Now that the Script Manager has done all the really hard work for us, we draw the line. We already knew lineStart, }
{ StyledLineBreak gave us lineBytes, which we pass to NTBDraw. It'll handle the different text alignments itself. }
{ Note that we really shouldn't draw text outside inWrapBox, but nonetheless it is necessary to break the entire text }
{ into lines, to have the correct line breaks: this means that we call NTBDraw only if curY <= inWrapBox.bottom + lineHeight }
{ ------------------------------------------------------------------------------------------------------ }
if curY <= inWrapBox.bottom + lineHeight then
NTBDraw(lineStartPtr, lineBytes, inWrapBox, inAlign, curY, boxWidth, breakCode);
{ ------------------------------------------------------------------------------------------------------ }
{ Now we advance our vertical position down by the height of one line, advance lineStartPtr by the number of bytes }
{ we just drew, calculate a new textLeft, and increment our line count. }
{ ------------------------------------------------------------------------------------------------------ }
curY := curY + lineHeight;
lineStartPtr := AddPtrLong(lineStartPtr, lineBytes);
textLeft := textLeft - lineBytes;
lineCount := lineCount + 1;
end;
{ --------------------------------------------------------------------------------------------------------- }
{ Well that was a job well done. Let's return some useful values, too. Stuff our ending vertical coordinate and the line }
{ height we calculated into outEndY and outLHUsed, respectively. These allow the guy to put something after the }
{ wrapped text (or at least know the right place to put it). }
{ --------------------------------------------------------------------------------------------------------- }
outEndY := curY - lineHeight;
outLhUsed := lineHeight;
{ --------------------------------------------------------------------------------------------------------- }
{ Finally, restore the clipping region (if requested), dispose of the region, and return the TOTAL number of lines drawn }
{ (note that we didn't stop drawing when curY advanced past inWrapBox->bottom. This way, the user could tell that the text }
{ overflowed inWrapBox. If you want to know how many lines fit, divide inWrapBox by outLhUsed. This way, you get the }
{ best of both worlds. }
{ --------------------------------------------------------------------------------------------------------- }
if inSwapClipping then
begin
SetClip(oldClip);
DisposeRgn(oldClip);
end;
NeoTextBox := lineCount;
end;
end.